package demo.logback;

import ch.qos.logback.classic.AsyncAppender;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.AsyncAppenderBase;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.OutputStreamAppender;
import ch.qos.logback.core.rolling.RollingFileAppender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.spi.ILoggingEvent;

/**
 * 项目分为三个模块：
 * <li>logback-core：其他俩模块基础模块，作为通用模块 其中没有 logger 的概念
 * <li>logback-classic：日志模块，完整实现了 SLF4J API
 * <li>logback-access：配合Servlet容器，提供 http 访问日志功能
 *
 * <p> 主要概念（Logger、Appender、Layout、Encoder）
 *
 * <p> log 整体流程：
 * <li>获取 filter 链决策
 * <li>应用 basic selection rule
 * <li>创建 LoggingEvent 对象
 * <li>调用 appenders
 * <li>调 Layout 格式化
 * <li>输出到目的地
 */
public class LogbackTest {

    private static final Logger logger = LoggerFactory.getLogger(LogbackTest.class);

    public static void logErr(String msg) {
        logger.error("logback : {}", msg);
    }

    /**
     * Logger：日志记录器，logback-classic 的部分。每个 Logger 都附加到一个 LoggerContext 上，该 Context 负责构造 Logger 以及将其安排在层级结构中。
     * <li>命名及层级关系：Logger 名称区分大小写，并遵循层级命名规则。层级关系用 "." 表示，如："com.foo" 是 "com.foo.Bar" 的父Logger。
     * 且所有 Logger 都可通过LoggerFactory#getlogger(String name)来获取，且相同名称返回的实例相同
     * <li>根 Logger：是所有层级结构的顶部Logger，可通过名称检索获取
     * <blockquote><pre>
     *     Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
     * </pre></blockquote>
     * <li>Level 与 继承关系：
     * Logger 可以被分配级别（TRACE、DEBUG、INFO、WARN、ERROR），可在 ch.qos.logback.classic 中查看
     * 若没有给 Logger 分配级别，则它将从最近的分配了等级的祖先处继承等级：日志记录器，记录日志的对象，可以记录日志，也可以记录其他日志记录器的日志
     * <li>Level 与 log 规则（basic selection rule）：Logger 只会启用等级 ≥ Logger等级的日志请求等级排序（严重程度，而非优先级）：TRACE < DEBUG < INFO < WARN < ERROR
     */
    public void demoLogger() {
    }

    /**
     * Appender就是用来处理logback框架下日志输出事件的组件。
     * Logback 通过Appender#doAppend(E event)将日志事件打印到目的地（允许附加多个Appender即即多个目的地
     * 目前官方已提供 console、文件、远程socket服务、JMS、远程UNIX Syslog进程、MySQL/PostgreSQL/Orcale等数据库的 appender 支持.
     * 注意：
     *
     * <li>一个 Logger 可以通过Logger#addAppender方法可被附加多个 appender
     * <li>appender 同样适用于继承结构，且是以追加的方式而非覆盖；
     * 但继承行为可被 Logger 的 additivity 标志影响是否继承（通过Logger#setAdditive设置)
     * <li>additivity 标志本身也是可继承的
     */
    public void demoAppender(Appender appender) {
        // OutputStreamAppender 是ConsoleAppender 和 FileAppender，FileAppender又是 RollingFileAppender 的超类
        OutputStreamAppender outputStreamAppender = new OutputStreamAppender();
        OutputStreamAppender fileAppender = new FileAppender();
        OutputStreamAppender consoleAppender = new ConsoleAppender();
        OutputStreamAppender rollingFileAppender = new RollingFileAppender();

        appender.doAppend(null);
        if (appender instanceof ConsoleAppender) {

        }
    }

    /**
     * 同步输出 VS 异步输出
     * <li>同步输出：传统的日志打印采用的是同步输出的方式，所谓同步日志，即当输出日志时，必须等待日志输出语句执行完毕后，才能执行后面的业务逻辑语句。
     * 使用logback的同步日志进行日志输出，日志输出语句与程序的业务逻辑语句将在同一个线程运行。
     * 在高并发场景下，日志数量不但激增，作为磁盘IO来说，容易产生瓶颈，导致线程卡顿在生成日志过程中，会影响程序后续的主业务，降低程序的性能。
     * <li>异步输出：使用异步日志进行输出时，日志输出语句与业务逻辑语句并不是在同一个线程中运行，而是有专门的线程用于进行日志输出操作，处理业务逻辑的主线程不用等待即可执行后续业务逻辑。
     * 这样即使日志没有完成输出，也不会影响程序的主业务，从而提高了程序的性能。
     * <p>
     * logback异步输出日志是通过AsyncAppender实现的。AsyncAppender可以异步的记录 ILoggingEvents日志事件。
     * 但是这里需要注意，AsyncAppender只充当事件分配器，它必须引用另一个Appender才能完成最终的日志输出。
     * logback的异步输出采用生产者消费者的模式，将生成的日志放入消息队列中，并将创建一个线程用于输出日志事件，有效的解决了这个问题，提高了程序的性能。
     * <p>
     * 默认情况下，event queue配置最大容量为256个events。如果队列已经满了，那么应用程序线程将被阻塞，无法记录新事件，直到工作线程有机会分派一个或多个事件。
     * 当队列不再达到最大容量时，应用程序线程可以再次开始记录事件。因此，当应用程序在其事件缓冲区的容量或附近运行时，异步日志记录就变成了伪同步。
     * 这未必是件坏事，AsyncAppender异步追加器设计目的是允许应用程序继续运行，尽管需要稍微多一点的时间来记录事件，直到附加缓冲区的压力减轻。
     * 优化 appenders 事件队列的大小以获得最大的应用程序吞吐量取决于几个因素。
     * 下列任何或全部因素都可能导致出现伪同步行为:
     * <li>大量的应用程序线程
     * <li>每个应用程序调用都有大量的日志事件
     * <li>每个日志事件都有大量数据
     * <li>子级appenders的高延迟
     * <li>为了保持事情的进展，增加队列的大小通常会有所帮助，代价是减少应用程序可用的堆。
     * <li>为了减少阻塞，在缺省情况下，当队列容量保留不到20% 时，AsyncAppender 将丢失 TRACE、 DEBUG 和 INFO 级别的事件，只保留 WARN 和 ERROR 级别的事件。
     * 这种策略确保了对日志事件的非阻塞处理(因此具有优异的性能) ，同时在队列容量小于20% 时减少 TRACE、 DEBUG 和 INFO 级别的事件。
     * 事件丢失可以通过将丢弃阈值属性设置为0(零)来防止。
     */
    public void demoAsyncAppender() {
        AsyncAppenderBase asyncAppender = new AsyncAppender();
        ILoggingEvent event = null;
        asyncAppender.doAppend(event);
    }

    /**
     * 表示日志输出格式，其通过接口ch.qos.logback.core#doLayout(E event): String将日志事件格式化为String 返回。
     * Logback 官方提供 PatternLayout，允许以类似c语言printf来指定输出格式。
     * 可通过将 layout 与 appender 关联，来实现自定义输出格式和目的地；但并不是每个 appender 都需要 layout，比如负责序列化的 SocketAppender 自然不需要 doLayout 转字符串
     */
    public void demoLayout() {

    }
}
